import { Notification } from 'element-ui'
import utils from '@/utils.js'

const conformanceJudge = (value, vnode) => {
  if (Number.isNaN(value)) {
    console.error(
      `在组件：${JSON.stringify(
        vnode.componentOptions.propsData
      )}上设置的计算公式计算结果为NaN，请检查公式。`
    )
    return false
  }
  return true
}

const formula = {
  bind(el, binding, vnode) {
    debugger
    if (!conformanceJudge(binding.value.value, vnode)) {
      return
    }
    const propsData = vnode.context.$options.propsData
    //初始化的时候，如果没有值，则用公式的计算结果来赋值给当前字段
    let oldVal = utils.getValueByPath(propsData, binding.value.bindPath)

    if (oldVal == undefined) {
      let pathAry = binding.value.bindPath.split('.')
      if (pathAry.length == 3) {
        vnode.context.$set(vnode.context.data[pathAry[1]], pathAry[2], '')
      } else if (pathAry.length == 4) {
        let key = /^(\w+)\[(\d+)\]$/.exec(pathAry[2])
        vnode.context.$set(
          vnode.context.data[pathAry[1]][key[1]][key[2]],
          pathAry[3],
          ''
        )
      }
    }
    if (
      !oldVal &&
      binding.value.value &&
      binding.value.value !== oldVal &&
      !binding.value.type
    ) {
      el.__vue__.$emit('input', binding.value.value)
    }
    if (propsData && !oldVal) {
      let value = binding.value.value
      if (binding.value.value instanceof Date) {
        value = binding.value.value.format(binding.value.format)
      }
      el.__vue__.$emit('input', value)
      utils.setValueByPath(propsData, binding.value.bindPath, value)
      vnode.context.$forceUpdate()
    }
  },
  update(el, binding, vnode) {
    if (!conformanceJudge(binding.value.value, vnode)) {
      return
    }
    const propsData = vnode.context.$options.propsData
    if (binding.oldValue.value instanceof Date) {
      if (binding.oldValue.value.getTime() !== binding.value.value.getTime()) {
        const value = binding.value.value.format(binding.value.format)
        el.__vue__.$emit('input', value)
        utils.setValueByPath(propsData, binding.value.bindPath, value)
        vnode.context.$forceUpdate()
      }
    } else {
      let oldVal = utils.getValueByPath(propsData, binding.value.bindPath)
      if (oldVal && binding.value.type && binding.value.type === 'default') {
        return
      }
      if (
        binding.oldValue.value != binding.value.value ||
        binding.oldValue.bindPath != binding.value.bindPath
      ) {
        // 在组件触发更新时：原始值和当前值不等 或者 原始路径和当前路径不等 才执行赋值和更新view操作
        el.__vue__.$emit('input', binding.value.value)
        utils.setValueByPath(
          propsData,
          binding.value.bindPath,
          binding.value.value
        )
        vnode.context.$forceUpdate()
      }
    }
  },
}

const Formulas = {
  d1900: new Date(Date.UTC(1900, 0, 1)),
  WEEK_STARTS: [
    undefined,
    0,
    1,
    undefined,
    undefined,
    undefined,
    undefined,
    undefined,
    undefined,
    undefined,
    undefined,
    undefined,
    1,
    2,
    3,
    4,
    5,
    6,
    0,
  ],
  /**
   * 冀中能源业务开发的计算公式
   */
  //根据传入的年月得到上月的月份
  LastMonth: (y, m) => {
    m = parseInt(m)
    y = parseInt(y)
    if (m > 0 && m < 13 && y > 0) {
      if (m === 1) {
        return 12
      }
      let v = m - 1
      if (v < 10) {
        v = '0' + v
      }
      return v
    }
  },
  //根据传入的年月得到上个月的年份
  LastYear: (y, m) => {
    m = parseInt(m)
    y = parseInt(y)
    if (m > 0 && m < 13 && y > 0) {
      if (m === 1) {
        return y - 1
      }
      return y
    }
  },

  /**
   * 数学公式
   */
  ABS: (number) => {
    number = Formulas.parseNumber(number)
    return Math.abs(number)
  },
  AVERAGE: (...number) => {
    let avg = 0
    number.forEach((item) => {
      avg += Formulas.parseNumber(item)
    })
    if (number.length === 0) return 0
    return avg / number.length
  },
  AVERAGESUB: (obj) => {
    let array = Formulas.getArrayByPath(obj)
    return Formulas.AVERAGE(...array)
  },
  CEILING: (number, significance) => {
    number = Formulas.parseNumber(number)
    significance = Formulas.ABS(significance)
    if (significance === 0) {
      return 0
    }
    let precision = -Math.floor(Math.log(significance) / Math.log(10))
    if (number > 0) {
      return Formulas.ROUND(
        Math.ceil(number / significance) * significance,
        precision
      )
    } else {
      return Formulas.ROUND(
        Math.floor(number / significance) * significance,
        precision
      )
    }
  },
  COUNT: (...args) => {
    return args.length
  },
  COUNTSUB: (obj) => {
    let array = obj.data
    return array.length
  },
  COUNTIF: (list, condition) => {
    if (!condition.startsWith('>') && !condition.startsWith('<')) {
      if (isNaN(condition)) {
        condition = "==='" + condition + "'"
      } else {
        condition = '===' + condition
      }
    }
    let i = 0
    // eslint-disable-next-line
    list.forEach((item) => {
      if (eval('item' + condition)) {
        i++
      }
    })
    return i
  },
  COUNTIFSUB: (obj, condition) => {
    let array = Formulas.getArrayByPath(obj)
    return Formulas.COUNTIF(array, condition)
  },
  FIXED: (decimal, digits) => {
    decimal = Formulas.parseNumber(decimal)
    digits = Formulas.parseNumber(digits)
    return Number(decimal).toFixed(digits)
  },
  FLOOR: (number, significance) => {
    number = Formulas.parseNumber(number)
    significance = Formulas.ABS(significance)

    if (significance === 0) {
      return 0
    }
    let precision = -Math.floor(Math.log(significance) / Math.log(10))
    if (number > 0) {
      return Formulas.ROUND(
        Math.floor(number / significance) * significance,
        precision
      )
    } else {
      return Formulas.ROUND(
        Math.ceil(number / significance) * significance,
        precision
      )
    }
  },
  INT: (number) => {
    number = Formulas.parseNumber(number)
    return parseInt(number)
  },
  LARGE: (array, index) => {
    index = Formulas.parseNumber(index)
    return array.sort(function(a, b) {
      return b - a
    })[index - 1]
  },
  LARGESUB: (obj, index) => {
    let array = Formulas.getArrayByPath(obj)
    return Formulas.LARGE(array, index)
  },
  LOG: (number, base) => {
    number = Formulas.parseNumber(number)
    base = Formulas.parseNumber(base)
    if (base === 0) return 0
    return Math.log(number) / Math.log(base)
  },
  MAX: (...args) => {
    args = Formulas.parseNumberArray(args)
    if (args.length === 0) {
      return 0
    }
    return Math.max(...args)
  },
  MAXSUB: (obj) => {
    let array = Formulas.getArrayByPath(obj)
    return Formulas.MAX(...array)
  },
  MIN: (...args) => {
    args = Formulas.parseNumberArray(args)
    if (args.length === 0) {
      return 0
    }
    return Math.min(...args)
  },
  MINSUB: (obj) => {
    let array = Formulas.getArrayByPath(obj)
    return Formulas.MIN(...array)
  },
  MOD: (dividend, divisor) => {
    dividend = Formulas.parseNumber(dividend)
    divisor = Formulas.parseNumber(divisor)
    let modules = Math.abs(dividend % divisor)
    return divisor > 0 ? modules : -modules
  },
  POWER: (number, power) => {
    number = Formulas.parseNumber(number)
    power = Formulas.parseNumber(power)
    return Math.pow(number, power)
  },
  PRODUCT: (...args) => {
    let total = 1
    args.forEach((item) => {
      item = Formulas.parseNumber(item)
      total *= item
    })
    return args.length === 0 ? 0 : total
  },
  PRODUCTSUB: (obj) => {
    let array = Formulas.getArrayByPath(obj)
    return Formulas.PRODUCT(...array)
  },
  RAND: () => {
    return Math.random()
  },
  ROUND: (number, digit) => {
    return Math.round(number * 10 ** digit) / 10 ** digit
  },
  SMALL: (array, index) => {
    array = Formulas.parseNumberArray(array)
    return array.sort(function(a, b) {
      return a - b
    })[index - 1]
  },
  SMALLSUB: (obj, index) => {
    let array = Formulas.getArrayByPath(obj)
    return Formulas.SMALL(array, index)
  },
  SQRT: (number) => {
    number = Formulas.parseNumber(number)
    return Math.sqrt(number)
  },
  SUM: (...args) => {
    let num = 0
    args.forEach((item) => {
      item = Formulas.parseNumber(item)
      num += item
    })
    return num
  },
  SUMSUB: (obj) => {
    let array = Formulas.getArrayByPath(obj)
    return Formulas.SUM(...array)
  },
  SUMPRODUCT: (...arrays) => {
    if (!arrays) return 0
    let min = Number.MAX_VALUE
    let total = 0
    arrays.forEach((array) => {
      if (array.length < min) {
        min = array.length
      }
    })
    for (let i = 0; i < min; i++) {
      let p = 1
      for (let j = 0; j < arrays.length; j++) {
        p *= Formulas.parseNumber(arrays[j][i])
      }
      total += p
    }
    return total
  },
  SUMPRODUCTSUB: (...arrays) => {
    if (!arrays) return 0
    let newArrays = []
    arrays.forEach((obj) => {
      newArrays.push(Formulas.getArrayByPath(obj))
    })
    return Formulas.SUMPRODUCT(...newArrays)
  },
  /**
   * 文本函数
   * @param args
   * @returns {string}
   * @constructor
   */
  CONCATENATE: (...args) => {
    let str = ''
    args.forEach((item) => {
      if (item) {
        str += item
      }
    })
    return str
  },
  CHAR: (number) => {
    number = Formulas.parseNumber(number)
    return String.fromCharCode(number)
  },
  EXACT: (str1, str2) => {
    return str1 === str2
  },
  ISEMPTY: (str) => {
    return str ? true : false
  },
  LEFT: (str, number) => {
    number = Formulas.parseNumber(number)
    return str.substr(str, number)
  },
  LEN: (str) => {
    return str.length
  },
  LOWER: (str) => {
    return str.toLowerCase()
  },
  MID: (str, start, length) => {
    return str.substr(start - 1, length)
  },
  REPLACE: (str, start, length, newStr) => {
    let replaceStr = Formulas.MID(str, start, length)
    if (!replaceStr) return str + newStr
    return str.replace(replaceStr, newStr)
  },
  REPT: (str, count) => {
    let result = ''
    for (let i = 0; i < Formulas.parseNumber(count); i++) {
      result += str
    }
    return result
  },
  RIGHT: (str, length) => {
    return str.substr(str.length - length, str.length)
  },
  UPPER: (str) => {
    return str.toUpperCase()
  },
  TEXT: (number) => {
    return number + ''
  },
  VALUE: (str) => {
    return Formulas.parseNumber(str)
  },
  TRIM: (str) => {
    return str.trim()
  },
  COUNTNO: (field, char) => {
    if (field == '') {
      return 0
    } else if (field.indexOf(char) > -1) {
      return field.split(char).length
    } else {
      return 1
    }
  },
  //逻辑函数
  AND: (...args) => {
    let result = true
    args.forEach((arg) => {
      if (!arg) {
        result = false
      }
    })
    return result
  },
  FALSE: () => {
    return false
  },
  IF: (arg, then_value, otherwise_value) => {
    return arg ? then_value : otherwise_value
  },
  NOT: (arg) => {
    return !arg
  },
  OR: (...args) => {
    let result = false
    args.forEach((arg) => {
      if (arg) {
        result = true
      }
    })
    return result
  },
  TRUE: () => {
    return true
  },
  XOR: (...args) => {
    let count = 0
    args.forEach((arg) => {
      if (!arg) {
        count++
      }
    })
    return count !== args.length && count !== 0
  },
  //时间函数
  DATE: (timestamp) => {
    let date = Formulas.parseDate(timestamp)
    if (date == 'Invalid Date') {
      Notification.error({
        title: '错误',
        message: '无效日期',
      })
      return
    }
    return date
  },
  DATEDELTA: (string, days) => {
    let timestamp = new Date(string).getTime()
    timestamp += days * 86400000
    return Formulas.DATE(timestamp)
  },
  HALFDAY: (startDate, endDate) => {
    let half = 0.5
    let startDay = Formulas.second(Formulas.parseStringToDate(startDate))
    let endDay = Formulas.second(Formulas.parseStringToDate(endDate))
    if (isNaN(startDay) || isNaN(endDay)) {
      return ''
    }
    let halfDay = Math.ceil((endDay - startDay) / 43200)
    return halfDay * half
  },
  COUNTHOURS: (startDate, endDate) => {
    let startDay = Formulas.second(Formulas.parseStringToDate(startDate))
    let endDay = Formulas.second(Formulas.parseStringToDate(endDate))
    if (isNaN(startDay) || isNaN(endDay)) {
      return ''
    }
    let hours = Math.ceil((endDay - startDay) / 3600)
    return hours
  },
  DAY: (timestamp) => {
    let date = Formulas.DATE(timestamp)
    return date.getDate()
  },
  DAYS: (startDate, endDate) => {
    let startDay = Formulas.serial(Formulas.parseStringToDate(startDate))
    let endDay = Formulas.serial(Formulas.parseStringToDate(endDate))
    if (isNaN(startDay) || isNaN(endDay)) {
      return ''
    }
    return endDay - startDay
  },
  DAYS360: (startDate, endDate, method) => {
    if (!startDate || !endDate) {
      return 0
    }
    startDate = Formulas.parseStringToDate(startDate)
    endDate = Formulas.parseStringToDate(endDate)
    let sm = startDate.getMonth()
    let em = endDate.getMonth()
    let sd, ed
    if (method) {
      sd = startDate.getDate() === 31 ? 30 : startDate.getDate()
      ed = endDate.getDate() === 31 ? 30 : endDate.getDate()
    } else {
      let smd = new Date(startDate.getFullYear(), sm + 1, 0).getDate()
      let emd = new Date(endDate.getFullYear(), em + 1, 0).getDate()
      sd = startDate.getDate() === smd ? 30 : startDate.getDate()
      if (endDate.getDate() === emd) {
        if (sd < 30) {
          em++
          ed = 1
        } else {
          ed = 30
        }
      } else {
        ed = endDate.getDate()
      }
    }

    return (
      360 * (endDate.getFullYear() - startDate.getFullYear()) +
      30 * (em - sm) +
      (ed - sd)
    )
  },
  HOUR: (timestamp) => {
    let date = Formulas.DATE(timestamp)
    return date.getHours()
  },
  ISOWEEKNUM: (date) => {
    date = Formulas.parseStringToDate(date)
    date.setHours(0, 0, 0)
    date.setDate(date.getDate() + 4 - (date.getDay() || 7))
    let yearStart = new Date(date.getFullYear(), 0, 1)
    return Math.ceil(((date - yearStart) / 86400000 + 1) / 7)
  },
  MINUTE: (timestamp) => {
    let date = Formulas.DATE(timestamp)
    return date.getMinutes()
  },
  MONTH: (timestamp) => {
    let date = Formulas.DATE(timestamp)
    return date.getMonth() + 1
  },
  NOW: () => {
    return Formulas.parseDate(new Date().getTime())
  },
  SECOND: (timestamp) => {
    let date = Formulas.DATE(timestamp)
    return date.getSeconds()
  },
  SYSTIME: () => {
    //todo
  },
  TIME: (hour, minute, second) => {
    hour = Formulas.parseNumber(hour)
    minute = Formulas.parseNumber(minute)
    second = Formulas.parseNumber(second)
    hour = hour < 0 ? 0 : hour
    minute = minute < 0 ? 0 : minute
    second = second < 0 ? 0 : second
    return (3600 * hour + 60 * minute + second) / 86400
  },
  TIMESTAMP: (date) => {
    if (date) {
      date = Formulas.parseStringToDate(date)
      return date.getTime()
    }
  },
  TODAY: () => {
    return new Date().format(
      utils.isMobile() ? 'yyyy/MM/dd HH:mm:ss' : 'yyyy-MM-dd HH:mm:ss'
    )
  },
  WEEKNUM: (date, returnType) => {
    if (!date) {
      return
    }
    date = Formulas.parseStringToDate(date)
    if (returnType === undefined) {
      returnType = 1
    }
    if (returnType === 21) return Formulas.ISOWEEKNUM(date)
    let week_start = Formulas.WEEK_STARTS[returnType]
    let jan = new Date(date.getFullYear(), 0, 1)
    let inc = jan.getDay() < week_start ? 1 : 0
    jan -= Math.abs(jan.getDay() - week_start) * 24 * 60 * 60 * 1000

    return Math.floor((date - jan) / (1000 * 60 * 60 * 24) / 7 + 1) + inc
  },
  YEAR: (timestamp) => {
    let date = new Date(timestamp)
    return date.getFullYear()
  },
  UUID: () => {
    function u4() {
      return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1)
    }
    return (
      u4() +
      u4() +
      '-' +
      u4() +
      '-' +
      u4() +
      '-' +
      u4() +
      '-' +
      u4() +
      u4() +
      u4()
    )
  },
  WEEKDAY: (date, returnNum) => {
    if (!date) {
      return
    }
    date = Formulas.parseStringToDate(date)
    const weekDayDic = ['日', '一', '二', '三', '四', '五', '六']
    const weekDay = date.getDay()
    return returnNum ? weekDay : `星期${weekDayDic[weekDay]}`
  },
  //高级函数:
  CURRENCY: (x) => {
    let tmp = Formulas.parseNumber(x)
    //TODO 转换为人民币大写
    return utils.convertCurrency(tmp)
  },
  THOUSAND: (x) => {
    if (x || x === 0) {
      x = x + ''
    }
    //let tmp = Formulas.parseNumber(x);
    let reg = /(\d)(?=(?:\d{3})+\b)/g
    return x && x.replace(reg, '$1,')
  },
  /**
   * 异常统一处理
   * @param {*} err
   * @param {*} typeName
   */
  throwErr(err, typeName) {
    Notification.error({
      title: typeName + '公式运算错误',
      message: err,
    })
    throw err
  },
  /**
   * 类型判断
   * @param {*} value
   * @param {*} type
   */
  typeofType(value, type) {
    if (value !== '' && typeof value != type) {
      throw '传入参数必须是' + type + '类型'
    }
  },
  parseNumber(number) {
    number = parseFloat(number)
    if (isNaN(number)) return 0
    return number
  },
  parseNumberArray(array) {
    return array.map((item) => {
      return Formulas.parseNumber(item)
    })
  },
  parseDate(timestamp) {
    timestamp = Formulas.parseNumber(timestamp)
    return new Date(timestamp)
  },
  parseStringToDate(str) {
    if (!str) {
      return new Date('')
    }
    return new Date(Date.parse(str.replace(/-/g, '/')))
  },
  getArrayByPath(obj) {
    let array = []
    if (!obj.data) return array
    obj.data.forEach((item) => {
      array.push(Formulas.parseNumber(item[obj.path]))
    })
    return array
  },
  serial(date) {
    let addOn = date > -2203891200000 ? 2 : 1
    return Math.ceil((date - Formulas.d1900) / 86400000) + addOn
  },
  second(date) {
    let addOn = date > -2203891200000 ? 2 : 1
    return Math.ceil((date - Formulas.d1900) / 1000) + addOn
  },
}

Formulas.install = (Vue) => {
  Vue.prototype.$Formulas = Formulas
}

export { formula, Formulas }
